#include "os_util.hh"
#include "string_util.hh"

#ifdef WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN // avoid including unnecessary WIN32 header files
#endif
#include "shlwapi.h"
#include <windows.h>
#pragma comment(lib, "shlwapi.lib")
#include "../detail/stack_trace_windows.hh"
#include <WinSock2.h>
#include <direct.h>
#define DELIMETER "\\"
#else
#include "../detail/stack_trace_linux.hh"
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define DELIMETER "/"
#endif // WIN32

#include <climits>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <random>

namespace kratos {
namespace util {

std::string get_host_ip(const std::string &host) {
  if (host.empty()) {
    return "";
  }
  if (host == "localhost") {
    return "127.0.0.1";
  }
  bool isIp = is_ip_address(host);
  if (isIp) {
    return host;
  }
  auto ent = gethostbyname(host.c_str());
  if (!ent) {
    return host;
  }
  auto ip = inet_ntoa(*((struct in_addr *)ent->h_addr));
  if (!ip) {
    return "";
  }
  return ip;
}

std::string get_binary_path() {
  const static int SIZE = 512;
  char path[SIZE] = {0};
#ifdef WIN32
  ::GetModuleFileNameA(NULL, path, sizeof(path));
#else
  int result = readlink("/proc/self/exe", path, sizeof(path));
  if (result < 0 || (result >= SIZE - 1)) {
    return "";
  }
  path[result] = '\0';
  for (int i = result; i >= 0; i--) {
    if (path[i] == '/') {
      path[i] = '\0';
      break;
    }
  }
  return path;
#endif // WIN32
  std::string temp(path);
  size_t index = temp.rfind("\\");
  std::string execPath;
  execPath.append(temp, 0, index);
  return execPath;
}

std::string get_binary_name() {
  const static int SIZE = 512;
  char path[SIZE] = {0};
#ifdef WIN32
  ::GetModuleFileNameA(NULL, path, sizeof(path));
#else
  int result = readlink("/proc/self/exe", path, sizeof(path));
  if (result < 0 || (result >= SIZE - 1)) {
    return "";
  }
  path[result] = '\0';
#endif // WIN32
  return util::get_file_name(path);
}

bool get_file_in_directory(const std::string &directory,
                           const std::string suffix,
                           std::vector<std::string> &fileNames) {
  if (!is_path_exists(directory)) {
    return false;
  }
#ifdef WIN32
  WIN32_FIND_DATAA findData;
  HANDLE handle = INVALID_HANDLE_VALUE;
  std::string path = directory + "\\*.*";
  handle = ::FindFirstFileA(path.c_str(), &findData);
  if (handle == INVALID_HANDLE_VALUE) {
    return false;
  }
  for (;;) {
    if (strcmp(findData.cFileName, ".") == 0 ||
        strcmp(findData.cFileName, "..") == 0) {
      if (!::FindNextFileA(handle, &findData)) {
        break;
      }
      continue;
    }
    std::string fullName = directory + "\\" + findData.cFileName;
    if (suffix.empty()) {
      fileNames.push_back(fullName);
    } else {
      if (endWith(fullName, suffix)) {
        fileNames.push_back(fullName);
      }
    }
    if (!::FindNextFileA(handle, &findData)) {
      break;
    }
  }
  ::FindClose(handle);
#else
  DIR *dir = 0;
  struct dirent *ent = 0;
  dir = opendir(directory.c_str());
  if (!dir) {
    return false;
  }
  while (0 != (ent = readdir(dir))) {
    if (!(ent->d_type & DT_DIR)) {
      if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
        continue;
      }
      std::string fullName = directory + "/" + ent->d_name;
      if (suffix.empty()) {
        fileNames.push_back(fullName);
      } else {
        if (endWith(fullName, suffix)) {
          fileNames.push_back(fullName);
        }
      }
    }
  }
#endif // WIN32
  return true;
}

bool is_path_exists(const std::string &path) {
#ifdef WIN32
  return (TRUE == ::PathFileExistsA(path.c_str()));
#else
  return !access(path.c_str(), F_OK);
#endif // WIN32
}

bool remove_file(const std::string &filePath) {
  return (::remove(filePath.c_str()) == 0);
}

std::string complete_path(const std::string &base, const std::string &sub) {
  if (endWith(base, DELIMETER)) {
    return base + sub;
  } else {
    return base + DELIMETER + sub;
  }
}

std::string complete_path_url(const std::string &base, const std::string &sub) {
  if (endWith(base, "/")) {
    return base + sub;
  } else {
    return base + "/" + sub;
  }
}

bool rename_file(const std::string &srcFileName,
                 const std::string &newFileName) {
  return (rename(srcFileName.c_str(), newFileName.c_str()) == 0);
}

std::string get_file_ext(const std::string &fileName) {
  auto pos = fileName.find_last_of('.');
  return std::string(fileName.begin() + pos + 1, fileName.end());
}

std::uint32_t get_random_uint32(std::uint32_t a, std::uint32_t b) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<std::uint32_t> dist(a, b);
  return dist(mt);
}

auto _make_dir(const std::string &path) -> bool {
#ifdef WIN32
  return (::_mkdir(path.c_str()) == 0);
#else
  return (::mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0);
#endif
}

auto make_dir(const std::string &path) -> bool {
  std::vector<std::string> result;
  util::split(path, DELIMETER, result);
  if (result.empty()) {
    return false;
  }
  std::string full_path;
  for (const auto &name : result) {
    if (full_path.empty()) {
      full_path = name;
    } else {
      full_path = complete_path(full_path, name);
    }
    if (!is_path_exists(full_path)) {
      if (!_make_dir(full_path)) {
        return false;
      }
    }
  }
  return true;
}

auto rm_empty_dir(const std::string &path) -> bool {
#ifdef WIN32
  return (::_rmdir(path.c_str()) == 0);
#else
  return (::rmdir(path.c_str()) == 0);
#endif
}

auto get_pid() -> int {
#ifndef WIN32
  return getpid();
#else
  return (int)::GetCurrentProcessId();
#endif // WIN32
}

auto get_current_stack_trace_info(int depth) -> std::string {
  return util::StackTrace::get_stack_frame_info(depth);
}

auto get_last_modify_time(const std::string &path) -> std::time_t {
#ifndef WIN32
  struct stat st;
  memset(&st, 0, sizeof(st));
  if (lstat(path.c_str(), &st) < 0) {
    return 0;
  }
  return st.st_mtime;
#else
  struct _stat st;
  int result = _stat(path.c_str(), &st);
  if (result) {
    return 0;
  }
  return st.st_mtime;
#endif // WIN32
}

auto get_file_content(const std::string &file_path, int line, int related_line,
                      std::string &content) -> bool {
  if (line <= 0 || related_line < 0) {
    return false;
  }
  content.clear();
  std::ifstream ifs;
  ifs.open(file_path);
  if (!ifs) {
    return false;
  }
  auto begin_line = line - related_line > 0 ? line - related_line : 0;
  int cur_line = 0;
  std::string line_str;
  while (std::getline(ifs, line_str)) {
    cur_line += 1;
    if (cur_line > line + related_line) {
      break;
    }
    if (cur_line >= begin_line) {
      if (cur_line == line) {
        content += "* " + std::to_string(cur_line) + "  " + line_str + "\n";
      } else {
        content += "  " + std::to_string(cur_line) + "  " + line_str + "\n";
      }
    }
  }
  return true;
}

} // namespace util
} // namespace kratos
